// by Rowan Copley, August and September 2012.
//some code from Monster Milktruck demo from Google Earth demos

window.arwing = null;

// Pull the Milktruck model from 3D Warehouse.
var PAGE_PATH = document.location.href.replace(/\/[^\/]+$/, '/');
var MODEL_URL = 
'http://gogo-cow-shooter.googlecode.com/files/arwing_fixed.zip';
//'http://sketchup.google.com/3dwarehouse/download?mid=3c9a1cac8c73c61b6284d71745f1efa9&rtyp=zip&fn=milktruck&ctyp=milktruck';
var INIT_LOC = {
  lat: 40.258045,
  lon: -105.851581,
  heading: 0
}; //Boulder, CO: 39.97, -105.25

var TICK_MS = 66;

var BALLOON_FG = '#000000';
var BALLOON_BG = '#FFFFFF';

var CAM_HEIGHT = 7.5;
var TRAILING_DISTANCE = 25;

function Arwing() {
  var me = this;

  me.doTick = true;
  
  me.localAnchorLla = [0, 0, 0];
  me.localAnchorCartesian = V3.latLonAltToCartesian(me.localAnchorLla);
  me.localFrame = M33.identity();

  // Position, in local cartesian coords.
  me.pos = [0, 0, 0];
  
  // Velocity, in local cartesian coords.
  me.vel = [0, 0, 0];
  me.dir = [0, 1, 0];
  
  //fighting trousers
  me.health = 100;
  me.death = false;
  me.timeOfDeath = 0.0;

  // Orientation matrix, transforming model-relative coords into local
  // coords.
  me.modelFrame = M33.identity();
  me.roll = 0;
  me.rollSpeed = 0;
  me.tilt = 0;
  me.heading = 0;
  me.headingSpeed = 0;
  me.tiltSpeed = 0;
  me.idleTimer = 0;
  me.fastTimer = 0;
  ge.getOptions().setMouseNavigationEnabled(false);
  ge.getOptions().setFlyToSpeed(100);  // don't filter camera motion
  window.google.earth.fetchKml(ge, MODEL_URL,  function(obj) { me.finishInit(obj); });
  
}

Arwing.prototype.finishInit = function(kml){
var me = this;
walkKmlDom(kml, function() {
    if (this.getType() == 'KmlPlacemark' &&
        this.getGeometry() &&
        this.getGeometry().getType() == 'KmlModel')
      me.placemark = this;
  });
  
  me.model = me.placemark.getGeometry();
  me.orientation = me.model.getOrientation();
  me.location = me.model.getLocation();
  
  me.model.setAltitudeMode(ge.ALTITUDE_ABSOLUTE);
  me.orientation.setHeading(0);
  me.model.setOrientation(me.orientation);

  ge.getFeatures().appendChild(me.placemark);

  me.balloon = ge.createHtmlStringBalloon('');
  me.balloon.setFeature(me.placemark);
  me.balloon.setMaxWidth(700);
  me.balloon.setForegroundColor(BALLOON_FG);
  me.balloon.setBackgroundColor(BALLOON_BG);

  me.lastMillis = (new Date()).getTime();

  me.shadow = ge.createGroundOverlay('');
  me.shadow.setVisibility(false);
  me.shadow.setIcon(ge.createIcon(''));
  me.shadow.setLatLonBox(ge.createLatLonBox(''));
  me.shadow.setAltitudeMode(ge.ALTITUDE_CLAMP_TO_SEA_FLOOR);
  me.shadow.getIcon().setHref(PAGE_PATH + 'shadowrect.png');
  me.shadow.setVisibility(true);
  ge.getFeatures().appendChild(me.shadow);

  google.earth.addEventListener(ge, "frameend", function() { me.tick(); });

  var location = me.model.getLocation();
  var camLocation = location;
  
  me.cameraCut();

  // Make sure keyboard focus starts out on the page.
  ge.getWindow().blur();

  // If the user clicks on the Earth window, try to restore keyboard
  // focus back to the page.
  google.earth.addEventListener(ge.getWindow(), "mouseup", function(event) {
      ge.getWindow().blur();
    });
	
	//doGeocode(INIT_LOC.lat + "," + INIT_LOC.lon);
	if(planet == "earth"){
		me.teleportTo(INIT_LOC.lat, INIT_LOC.lon, INIT_LOC.heading);
	}else if(planet == "mars"){
		me.teleportTo(18.4, -133);
		//me.setAltitude(100000);
	}
}

leftButtonDown = false;
rightButtonDown = false;
upButtonDown = false;
downButtonDown = false;
spaceButtonDown = false;
plusButtonDown = false;

function keyDown(event) {
  if (!event) {
    event = window.event;
  }
  if (event.keyCode == 37) {  // Left: roll left
    leftButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 39) {  // Right: roll right
    rightButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 38) {  // Up: pitch down
    upButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 40) {  // Down: pitch up
    downButtonDown = true;
    event.returnValue = false;
  } else if (event.keyCode == 107){ //Numpad Plus: speed boost
	plusButtonDown = true;
	event.returnValue = false;
  } else if (event.keyCode == 32){  //Space: shoot
	spaceButtonDown = true;
	shotFired = false;
	event.returnValue = false;
  } else {
    return true;
  }
  return false;
}

function keyUp(event) {
  if (!event) {
    event = window.event;
  }
  if (event.keyCode == 37) {  // Left.
    leftButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 39) {  // Right.
    rightButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 38) {  // Up.
    upButtonDown = false;
    event.returnValue = false;
  } else if (event.keyCode == 40) {  // Down.
    downButtonDown = false;
    event.returnValue = false;
  }else if (event.keyCode == 107){ //Numpad Plus
	plusButtonDown = false;
	event.returnValue = false;
  } else if (event.keyCode == 32){ //Space
	spaceButtonDown = false;
	shotFired = false;
	ge.setBalloon(null);
	event.returnValue = false;
  }
  return false;
}

function clamp(val, min, max) {
  if (val < min) {
    return min;
  } else if (val > max) {
    return max;
  }
  return val;
}

Arwing.prototype.fireShot = function(){
	var me = this;
	//need to do a walkdown of the model
	//play a sound
	var soundManager = new SoundManager(null,null);
	sound = soundManager.createSound({id: 0, url: '01-Intro.mp3', volume: 1});
	//sound.load();
	//sound.play();
	me.balloon.setContentString("BOOM");
	ge.setBalloon(me.balloon);
}

Arwing.prototype.calculateDirection = function(){
	var me = this;
	var tiltRadians = me.tilt * (Math.PI / 180.0);
	var rollRadians = me.roll * (Math.PI / 180.0);
	var theta = Math.sin(tiltRadians)*Math.sin(rollRadians);
	var phi = Math.cos(rollRadians)*Math.sin(tiltRadians);
	
	//TODO: incorporate heading and improve the direction to be more realistic
	me.dir = [Math.sin(-rollRadians),
					(1-phi) * (1 - theta),
					-phi];
	me.dir = V3.normalize(me.dir);
}

Arwing.prototype.calculateVelocity = function(){
	var me = this;
	var airDensity = (100000 - me.pos[2]) / 100000;
	var absSpeed = V3.length(me.vel);
	if(plusButtonDown){		
		//speed limiter
		if(absSpeed > 10000 && me.pos[2] < 100000){
		//leaves the absolute speed unchanged but slowly changes direction
			me.vel = V3.add(me.vel, V3.scale(me.dir, 25));
			me.dir = V3.normalize(me.vel);
			me.vel = V3.scale(me.dir, absSpeed);
		}else if(absSpeed < 20000){
		//increases speed in that direction
			//me.vel = V3.scale(me.dir, absSpeed * 1.005);
			me.vel = V3.add(me.vel, V3.scale(me.dir, 25));
		}
	}else{
		if(me.pos[2] < 100000){
			
			if(absSpeed > 300){
				//hackish atmospheric drag
				me.vel = V3.scale(me.dir, absSpeed - (1-airDensity) * 10);
			}else{
				me.vel = V3.scale(me.dir, 300);
			}
		}
	}
	
  //gravity hack
  if(me.pos[2] > 10000){
	if(!plusButtonDown){
		me.vel[2] -= 9.8 * 0.1/(0.1+airDensity);
	}
  }
	//me.vel = V3.scale(me.dir, 300);
}

Arwing.prototype.calculateHeading = function(dt){
	var me = this;
	me.headingSpeed = (me.tiltSpeed * 0.08) * (me.rollSpeed * 0.08);
	if(!rightButtonDown && !leftButtonDown){
		me.headingSpeed = me.heading * (-2);
	}
	me.heading += me.headingSpeed * dt;
	me.heading = clamp(me.heading, -45, 45);
	if(Math.abs(me.heading) < 1){
		me.heading = 0;
		me.headingSpeed = 0;
	}
	//me.balloon.setContentString(""+me.headingSpeed);
	//ge.setBalloon(me.balloon);
}

Arwing.prototype.calculateRoll = function(dt){
  var me = this;
   if(rightButtonDown){
      me.rollSpeed = -150;
   }else if(leftButtonDown){
      me.rollSpeed = 150;
   }else{
	      me.rollSpeed = 3 * me.roll * (-1);  
		  if(me.roll < 1 && me.roll > -1){
			me.rollSpeed = 0;
			me.roll = 0;
		  }
   }
  me.roll += me.rollSpeed * dt;
  me.roll = fixAngle(me.roll);
  me.roll = clamp(me.roll, -45, 45);
}

Arwing.prototype.calculateTilt = function(dt,groundAlt){
	var me = this;
	if(upButtonDown){
		me.tiltSpeed = 75;
	}else if(downButtonDown){
		me.tiltSpeed = -75;
	}else{
		me.tiltSpeed = 2*me.tilt * (-1);
		if(me.tilt < 1 && me.tilt > -1){
			me.tiltSpeed = 0;
			me.tilt = 0;
		  }
	}
	/*
	//altitude limiter
	if(me.pos[2]  - groundAlt > 2000 && !upButtonDown){
		me.tilt *= 1 - ((me.pos[2] - 2000)/1000);
		if(me.pos[2] > 3000){
			me.tilt = 0;
		}
	}
	*/
	me.tilt += me.tiltSpeed * dt;
	me.tilt = fixAngle(me.tilt);
	me.tilt = clamp(me.tilt, -45,45);
	if(me.pos[2] - groundAlt < 1){
		if(me.tilt > 30 || me.tilt < -30){
			me.tilt = -30;
		}
		me.tilt = clamp(me.tilt, -30, 0);
	}
}

Arwing.prototype.tick = function() {
  var me = this;
  var now = (new Date()).getTime();
  var me = this;
	var gpos = V3.add(me.localAnchorCartesian, M33.transform(me.localFrame, me.pos));
	var lla = V3.cartesianToLatLonAlt(gpos);
	var groundAlt = ge.getGlobe().getGroundAltitude(lla[0], lla[1]);
  // dt is the delta-time since last tick, in seconds
  var dt = (now - me.lastMillis) / 1000.0;
  if (dt > 0.25) {
    dt = 0.25;
  }
  me.lastMillis = now;
  
  if (V3.length([me.pos[0], me.pos[1], 0]) > 100) {
        me.adjustAnchor();
  }
  
  if(spaceButtonDown && !shotFired){
	shotFired = true;
	me.fireShot();
  }
  
  if(me.health <= 0 && me.doTick){
	if(!me.death){
		me.death = true;
		me.timeOfDeath = now;
	}
	var absSpeed = V3.length(me.vel);
	var timeSinceDeath = (now - me.timeOfDeath)/1000.0;
	
	if(timeSinceDeath > 2 && me.pos[2] - groundAlt < 0){
		me.balloon.setContentString("Mayday, mayday!");
		ge.setBalloon(me.balloon);
		//alert("NOOOOOOO!!!!");
		me.doTick = false;
	}else{
		me.pos[2] += (timeSinceDeath - timeSinceDeath*timeSinceDeath)*2;
	}
  }else if(me.doTick){
	  me.calculateDirection();
	  me.calculateVelocity();
	  me.pos = V3.add(me.pos, V3.scale(me.vel, dt));
	  
	  //don't go underground
	  if(me.pos[2] < groundAlt){
		me.pos[2] = groundAlt;
	  }
  }else{
	me.health = 100;
	me.doTick = true;
	if(planet == "earth"){
		showMars();
	}else{
		showEarth();
	}
  }
  
   // Propagate our state into Earth.
  gpos = V3.add(me.localAnchorCartesian, M33.transform(me.localFrame, me.pos));
  lla = V3.cartesianToLatLonAlt(gpos);
  me.model.getLocation().setLatLngAlt(lla[0], lla[1], lla[2]);

  //if dead, do death spiral; otherwise find orientation
  if(me.health <= 0 && me.doTick){
	var timeSinceDeath = (now - me.timeOfDeath)/1000.0;
	me.roll = fixAngle(timeSinceDeath*180);
  }else{
	  me.calculateHeading(dt);
	  me.calculateRoll(dt);
	  me.calculateTilt(dt,groundAlt);
  }
  
  //propogate orientation to model
  var newhtr = M33.localOrientationMatrixToHeadingTiltRoll(me.modelFrame);
  var absHeading = newhtr[0];
  var absTilt = newhtr[1];
  var absRoll = newhtr[2];
  absTilt += me.tilt;
  absRoll += me.roll;
  absHeading += me.heading;
  me.orientation.set(absHeading, absTilt, absRoll);

  //deal damage 
  if(me.pos[2] - groundAlt < 1){
	me.health -= 20;
	me.pos[2] += 2;
  }
  
  var latLonBox = me.shadow.getLatLonBox();
  var radius = .00005;
  latLonBox.setNorth(lla[0] - radius);
  latLonBox.setSouth(lla[0] + radius);
  latLonBox.setEast(lla[1] - radius);
  latLonBox.setWest(lla[1] + radius);
  latLonBox.setRotation(-newhtr[0]);
  
  //me.balloon.setContentString(""+me.pos[2]);
  //me.balloon.setContentString(lla[2]);
  //ge.setBalloon(me.balloon);
  
  //recalculate our position for camera's information
  gpos = V3.add(me.localAnchorCartesian, M33.transform(me.localFrame, me.pos));
  me.cameraCut();
  if(me.pos[2] > 10000000){
	//we've traveled far enough we might as well be going to mars
	showMars();
  }
  /*
  if(lla[2] > 80000){
		me.cameraCut();
  }else{
		me.cameraFollow(dt, gpos, me.localFrame);
  }
  */
};

// TODO: would be nice to have globe.getGroundNormal() in the API.
function estimateGroundNormal(pos, frame) {
  // Take four height samples around the given position, and use it to
  // estimate the ground normal at that position.
  //  (North)
  //     0
  //     *
  //  2* + *3
  //     *
  //     1
  var pos0 = V3.add(pos, frame[0]);
  var pos1 = V3.sub(pos, frame[0]);
  var pos2 = V3.add(pos, frame[1]);
  var pos3 = V3.sub(pos, frame[1]);
  var globe = ge.getGlobe();
  function getAlt(p) {
    var lla = V3.cartesianToLatLonAlt(p);
    return globe.getGroundAltitude(lla[0], lla[1]);
  }
  var dx = getAlt(pos1) - getAlt(pos0);
  var dy = getAlt(pos3) - getAlt(pos2);
  var normal = V3.normalize([dx, dy, 2]);
  return normal;
}

Arwing.prototype.scheduleTick = function() {
  var me = this;
  if (me.doTick) {
    setTimeout(function() { me.tick(); }, TICK_MS);
  }
};

// Cut the camera to look at me.
Arwing.prototype.cameraCut = function() {
  var me = this;
  var lo = me.model.getLocation();
  var la = ge.createLookAt('');
  var gpos = V3.add(me.localAnchorCartesian,
                    M33.transform(me.localFrame, me.pos));
  var lla = V3.cartesianToLatLonAlt(gpos);
  var groundAlt = ge.getGlobe().getGroundAltitude(lla[0], lla[1]);
  if(me.pos[2] > 100000){
	  var cameraTilt = ((988800 - me.pos[2]) / 1000000) * 90; //we want this to start at 80 and slowly transition to 45
	  cameraTilt += (80 - cameraTilt) * 0.01;
  }else{
		cameraTilt = 80;
  }
  if(cameraTilt < 65) 
	cameraTilt = 65;
  la.set(lo.getLatitude(), lo.getLongitude(),    
         (lla[2] - groundAlt) /* altitude */,			
         ge.ALTITUDE_RELATIVE_TO_SEA_FLOOR,
         fixAngle(me.model.getOrientation().getHeading()),
         cameraTilt, /* tilt */
         40 /* range */         
         );
  ge.getView().setAbstractView(la);
};

Arwing.prototype.cameraFollow = function(dt, arwingPos, localToGlobalFrame) {
  var me = this;

  var c0 = Math.exp(-dt / 0.5);
  var c1 = 1 - c0;

  var la = ge.getView().copyAsLookAt(ge.ALTITUDE_RELATIVE_TO_SEA_FLOOR);

  var arwingHeading = me.model.getOrientation().getHeading();
  var camHeading = la.getHeading();

  var deltaHeading = fixAngle(0 - camHeading);
  //var deltaHeading = 0;
  var heading = camHeading + c1 * deltaHeading;
  heading = fixAngle(heading);

  var headingRadians = heading / 180 * Math.PI;
  
  var headingDir = V3.rotate(localToGlobalFrame[1], localToGlobalFrame[2],
                             headingRadians);
  var camPos = V3.add(arwingPos, V3.scale(localToGlobalFrame[2], CAM_HEIGHT));
  camPos = V3.add(camPos, V3.scale(headingDir, -TRAILING_DISTANCE));
  var camLla = V3.cartesianToLatLonAlt(camPos);
  var camLat = camLla[0];
  var camLon = camLla[1];
  var camAlt = camLla[2] - ge.getGlobe().getGroundAltitude(camLat, camLon);

  la.set(camLat, camLon, camAlt, ge.ALTITUDE_RELATIVE_TO_SEA_FLOOR, 
        heading, 80 /*tilt*/, 0 /*range*/);
  ge.getView().setAbstractView(la);
};

// heading is optional.
Arwing.prototype.teleportTo = function(lat, lon, heading) {
  var me = this;
  var distAboveGround = 10;
  me.model.getLocation().setLatitude(lat);
  me.model.getLocation().setLongitude(lon);
  me.model.getLocation().setAltitude(ge.getGlobe().getGroundAltitude(lat, lon) + distAboveGround);
  if (heading == null) {
    heading = 0;
  }
  me.vel = [0, 0, 0];
  me.tilt = -15;

  me.localAnchorLla = [lat, lon, distAboveGround];
  me.localAnchorCartesian = V3.latLonAltToCartesian(me.localAnchorLla);
  me.localFrame = M33.makeLocalToGlobalFrame(me.localAnchorLla);
  me.modelFrame = M33.identity();
  me.modelFrame[0] = V3.rotate(me.modelFrame[0], me.modelFrame[2], -heading);
  me.modelFrame[1] = V3.rotate(me.modelFrame[1], me.modelFrame[2], -heading);
  me.pos = [0, 0, ge.getGlobe().getGroundAltitude(lat, lon)+ distAboveGround];

  me.cameraCut();
};

// Move our anchor closer to our current position.  Retain our global
// motion state (position, orientation, velocity).
Arwing.prototype.adjustAnchor = function() {
  var me = this;
  var oldLocalFrame = me.localFrame;

  var globalPos = V3.add(me.localAnchorCartesian,
                         M33.transform(oldLocalFrame, me.pos));
  var newAnchorLla = V3.cartesianToLatLonAlt(globalPos);
  newAnchorLla[2] = 0;  // For convenience, anchor always has 0 altitude.

  var newAnchorCartesian = V3.latLonAltToCartesian(newAnchorLla);
  var newLocalFrame = M33.makeLocalToGlobalFrame(newAnchorLla);

  var oldFrameToNewFrame = M33.transpose(newLocalFrame);
  oldFrameToNewFrame = M33.multiply(oldFrameToNewFrame, oldLocalFrame);

  var newVelocity = M33.transform(oldFrameToNewFrame, me.vel);
  var newModelFrame = M33.multiply(oldFrameToNewFrame, me.modelFrame);
  var newPosition = M33.transformByTranspose(
      newLocalFrame,
      V3.sub(globalPos, newAnchorCartesian));

  me.localAnchorLla = newAnchorLla;
  me.localAnchorCartesian = newAnchorCartesian;
  me.localFrame = newLocalFrame;
  me.modelFrame = newModelFrame;
  me.pos = newPosition;
  me.vel = newVelocity;
}

// Keep an angle in [-180,180]
function fixAngle(a) {
  while (a < -180) {
    a += 360;
  }
  while (a > 180) {
    a -= 360;
  }
  return a;
}

Arwing.prototype.setAltitude = function(altitude){
	var me = this;
	var gpos = V3.add(me.localAnchorCartesian, M33.transform(me.localFrame, me.pos));
  var lla = V3.cartesianToLatLonAlt(gpos);
  me.model.getLocation().setLatLngAlt(lla[0], lla[1], altitude);
}
